import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;

import javax.swing.JFrame;


public class PNSimFrame extends JFrame implements MouseListener{

	
	private static final Color ColorMarker   = newColor(Color.getHSBColor(267 /360f, .95f, 1.00f), 1);
	private static final Color ColorSelBorder= newColor(Color.getHSBColor(0   /360f, .00f, 0.40f), 1);
	private static final Color ColorArrow    = newColor(Color.getHSBColor(267 /360f, .24f, 0.90f), 1);
	private static final Color ColorArrowSel = newColor(Color.getHSBColor(267 /360f, .90f, 1.00f), 1);
	private static final Color ColorPlace    = newColor(Color.getHSBColor(214 /360f, .45f, 1.00f), 0.5f);
	private static final Color ColorPlaceSel = newColor(Color.getHSBColor(214 /360f, .70f, 1.00f), 1);
	private static final Color ColorTrans    = newColor(Color.getHSBColor(20  /360f, .45f, 1.00f), 0.5f);
	private static final Color ColorTransSel = newColor(Color.getHSBColor(20  /360f, .70f, 1.00f), 1);

	public final PnmlData PNData;
	public ArrayList<KeyValueList> OutgoingMsgs = new ArrayList<KeyValueList>();
	public int SimDelayMS = 200;
	Object selected = null;
	private final NetSim.SISSender SISThread;
	private final NetSim.SISThread SISInThread;
	
	public PNSimFrame(PnmlData pnml, NetSim.SISThread inThread,  NetSim.SISSender outThread){
		PNData = pnml;
		SISThread = outThread;
		SISInThread = inThread;
		this.setDefaultCloseOperation(EXIT_ON_CLOSE);
		this.setSize(500, 350);
		this.setTitle("Callen's Petri Net Simulator for SIS Testbed - ("+pnml.getNetId()+")");
		this.getContentPane().addMouseListener(this);
		(new SimThread(this)).start();
		this.setVisible(true);
		this.createBufferStrategy(2);
	}

	class SimThread extends Thread {
		private PNSimFrame Sim;
		public SimThread(PNSimFrame owner) {
			setDaemon(true);
			Sim = owner;
		}
		public void run() {
			ArrayList<String> newTokenPlaceIds = new ArrayList<String>();
			while(true){
				if(!SISInThread.Refresh){
					newTokenPlaceIds.clear();
					for(PNTransition t : Sim.PNData.Transitions){
						boolean fire = true;
						if(t.InputIds.length < 1) continue;
						for(String s : t.InputIds)
							if(((PNPlace)Sim.PNData.getNodeById(s)).HasToken == false)
								fire = false;
						if(!fire) continue;
						for(String s : t.InputIds)
							((PNPlace)Sim.PNData.getNodeById(s)).HasToken = false;
						if(t.OnFire != null)
							OutgoingMsgs.add(t.OnFire);
						for(String s : t.OutputIds) 
							newTokenPlaceIds.add(s);
					}
					for(String s : newTokenPlaceIds) 
						((PNPlace)Sim.PNData.getNodeById(s)).HasToken = true;
				}
				else
					SISInThread.Refresh = false;
				Sim.repaint();
				MsgEncoder me = SISThread.ME;
				while(OutgoingMsgs.size() > 0){
					KeyValueList msg = OutgoingMsgs.get(OutgoingMsgs.size()-1);
					Socket s = SISThread.S;
					try {
						me.sendMsg(msg, s.getOutputStream());
					} catch (IOException e) {
						e.printStackTrace();
					}
					OutgoingMsgs.remove(msg);
				}
				try {
					Thread.sleep(Sim.SimDelayMS);
				} catch (InterruptedException e) {	}
				
			}
		}
	}
	
	
	public void paint(Graphics g) {
		Graphics2D g2d = null;
		try{
			g2d = (Graphics2D)this.getBufferStrategy().getDrawGraphics();
		}catch(Exception ex){return;}
		//set 0,0 to the exact corner of visible area
		g2d.translate(this.getInsets().left, this.getInsets().top);
		g2d.setColor(Color.WHITE);
		g2d.fillRect(0, 0, this.getWidth(), this.getHeight());
		drawArcs(g2d);
		for(PNPlace p : PNData.Places)
			drawPlace(g2d, p);
		for(PNTransition t : PNData.Transitions)
			drawTrans(g2d, t);
		this.getBufferStrategy().show();
	}

	
	void drawArcs(Graphics2D g) {
		PNPlace p;
		PNTransition t;
		for(PnmlData.ArcData ad : PNData.Arcs) { 
			try{
				p = (PNPlace)PNData.getNodeById(ad.SourceId);
				t = (PNTransition)PNData.getNodeById(ad.TargetId);
			}catch(Exception e){
				try{
					t = (PNTransition)PNData.getNodeById(ad.SourceId);
					p = (PNPlace)PNData.getNodeById(ad.TargetId);
				}catch(Exception ex){
					ex.printStackTrace();
					throw new RuntimeException("Shouldnta happened. " + ex);
				}
			}
			if(p == PNData.getNodeById(ad.SourceId)){
				//TODO: use all points, not just source and target
				
				drawArrow(g, ColorArrow, p.Position, t.Position);
			}
			else{
				drawArrow(g, ColorArrow, t.Position, p.Position);				
			}
		}
	}
	
	void drawArrow(Graphics2D g, Color c, Point from, Point to) {
		//Color cur = g.getColor();
		g.setColor(c);
		g.drawLine(from.x, from.y, to.x, to.y);
		
		double dx = from.x - to.x;
		double dy = from.y - to.y;
		double len = Math.sqrt(dx*dx + dy*dy);
		double ang = Math.atan2(dy, dx);
		double a1 = ang + Math.PI / 8f;
		double a2 = ang - Math.PI / 8f;
		//start arrow at (to + uv*5)
		double uvx = dx / len, uvy = dy / len;
		Point toOff = new Point((int)(to.x + uvx * 4),(int)(to.y + uvy * 4));
		
		g.drawLine(toOff.x, toOff.y, toOff.x + (int)(14 * Math.cos(a1)), toOff.y + (int)(14 * Math.sin(a1)));
		g.drawLine(toOff.x, toOff.y, toOff.x + (int)(14 * Math.cos(a2)), toOff.y + (int)(14 * Math.sin(a2)));
		//g.setColor(cur);
	}
	
	void drawPlace(Graphics2D g, PNPlace plc) {
		Point p = plc.Position;
		g.setColor(ColorPlace);
		if(selected == plc){
			g.setColor(ColorSelBorder);
			g.fillOval(p.x - plc.Radius - 1, p.y - plc.Radius - 1, plc.Radius*2 + 2, plc.Radius*2 + 2);
			g.setColor(ColorPlaceSel);
		}
		g.fillOval(p.x - plc.Radius, p.y - plc.Radius, plc.Radius*2, plc.Radius*2);
		if(plc.HasToken) {
			g.setColor(ColorMarker);
			g.fillOval(p.x - 5, p.y - 5, 10, 10);
		}
	}

	void drawTrans(Graphics2D g, PNTransition trs) {
		Point p = trs.Position;
		g.setColor(ColorTrans);
		if(selected == trs) {
			g.setColor(ColorSelBorder);
			g.fillRect(p.x - trs.Width/2 - 1, p.y - trs.Height/2 - 1, trs.Width + 2, trs.Height + 2);
			g.setColor(ColorTransSel);
		}
		g.fillRect(p.x - trs.Width/2, p.y - trs.Height/2, trs.Width, trs.Height);
	}

	static Color newColor(Color source, float alpha) {
		return new Color(source.getRed(), source.getGreen(), source.getBlue(), (int)(alpha*255));
	}

	@Override
	public void mouseClicked(MouseEvent arg0) {
		//NetSim.p("click at " + arg0.getPoint());
		this.selected = null;
		for(PNPlace pl : PNData.Places)
			if(pl.isClick(arg0.getPoint())) {
				(new PNDetailFrame(this,pl)).setVisible(true);
				selected = pl;
				break;
			}
		for(PNTransition tr : PNData.Transitions)
			if(tr.isClick(arg0.getPoint())) {
				(new PNDetailFrame(this,tr)).setVisible(true);				
				selected = tr;
				break;
			}
		this.repaint();
	}

	@Override
	public void mouseEntered(MouseEvent arg0) {}

	@Override
	public void mouseExited(MouseEvent arg0) {}

	@Override
	public void mousePressed(MouseEvent arg0) {}

	@Override
	public void mouseReleased(MouseEvent arg0) {}

}
